package poolserver

import (
	"net"
	"sync"
	"sync/atomic"
	"time"

	"go.uber.org/zap"
)

type PgBackendConn struct {
	Id               uint32 //一个后端连接的唯一标识
	Conn             net.Conn
	nextPrepareId    int32
	needClosePrepare []int32
	ReConnCnt        int32  //重连接次数，每重新连接一次时，此次数加1
	LastFrontId      uint32 //最后使用这个后端的前端ID
	ConnUnixTime     int64  //连接时的unix时间戳
	LifeUsedCount    uint32 //使用的次数
	ConnPortal       string //连接串的名称
	RwState          int8   //1-只读后端，2-读写后端
	HaAge            int32  //高可用HA的年龄，每切换一次，年龄加1，用此值与连接池中的HaAge比较，如果不相同，说明切换了，则RwState状态不是准确的，需要重新获得一个新连接
	Pid              uint32 // 数据库后端连接的pid
	BeKey            uint32 // 数据库后端的Backend Key data，cancel此后端时，需要提供此key
	Lock             sync.Mutex
}

func (p *PgBackendConn) Init() {
	p.needClosePrepare = make([]int32, 0, 10)
	p.nextPrepareId = 0
	p.ReConnCnt = 0
}

func (p *PgBackendConn) AllocPrepareId() int32 {
	var ret int32
	//FIXME: 是否会溢出？
	ret = atomic.AddInt32(&p.nextPrepareId, 1)
	return ret
}

func (p *PgBackendConn) DeletePrepareId(prepareId int32) {
	p.Lock.Lock()
	defer p.Lock.Unlock()
	p.needClosePrepare = append(p.needClosePrepare, prepareId)
}

func (p *PgBackendConn) FreshConn(pool *Pool) string {
	var err error
	var portal string
	var conn net.Conn
	var pid uint32
	var bekey uint32

	portal = p.ConnPortal
	zap.S().Infof("Reconnect for refresh: %s", p.ConnPortal)
	conn, err, pid, bekey = connectBackend(p.ConnPortal, pool.BeUser, pool.BePasswd, pool.BeDbName)
	if err != nil {
		zap.S().Infof("Refresh failed because could not connect(%s): %s", portal, err.Error())
	} else {
		p.Lock.Lock()
		p.Conn.Close()
		p.Conn = conn
		p.Pid = pid
		p.BeKey = bekey
		p.ConnUnixTime = time.Now().Unix()
		p.ReConnCnt++
		p.Lock.Unlock()
	}
	return portal
}

func (p *PgBackendConn) GetBeKeyData() (string, uint32, uint32) {
	p.Lock.Lock()
	defer p.Lock.Unlock()
	return p.ConnPortal, p.Pid, p.BeKey
}

// CleanupNeedClosePrepare 清除已记录为需要关闭的prepare
func (p *PgBackendConn) CleanupNeedClosePrepare() {
	var err error
	p.Lock.Lock()
	defer p.Lock.Unlock()
	var prepareId int32
	for _, prepareId = range p.needClosePrepare {
		err = sendPqClosePrepare(p.Conn, prepareId)
		if err != nil {
			break
		}
		err = sendPqSync(p.Conn)
		if err != nil {
			break
		}

		var buf [1024]byte
		for {
			_, _, err = recvMessage(p.Conn, buf[:])
			if err != nil {
				zap.S().Infof("Recv from PoolConfig error: %s", err.Error())
				return
			}
			if buf[0] == 'Z' { /*ReadyForQuery*/
				break
			}
			if buf[0] == 'E' {
				zap.S().Infof("Cleanup prepare failed!")
			}
		}
	}
	p.needClosePrepare = nil
}
